#include <iostream>
#include <cstdlib>
#include <sstream>
#include <cctype>

#include "mem.h"
#include "parse.h"
#include "cmd.h"
#include "error.h"

using namespace std;

void varIs(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    /*
        The 'var' keyword has the following syntax:
            var <variable name> is <variable type>;

        At this point 'idx' points to the keyword 'var'. Check if at least 4 tokens exist for
        the current statement, including the semicolon, otherwise throw an error.
    */
    bool state;         // Store the return value of the 'addVar' function
    string varName;     // Buffer for storing the variable name
    int count=0;        // Keeps count as we iterate through the syntax

    /* Try to increment the index. It will now point to the variable name. */
    (unsigned int)(idx+1) < tokens.size() ? ++idx : idx;

    while(idx < tokens.size())
    {
        if(count == 0)
        {
            /* Make sure that this is not a quoted string by checking the presence of the marker - 0xFF */
            if((unsigned char)tokens.at(idx).at(0) == 0xFF)
                errorMessage(ERR_EXPECTED_IDENT, "foo", nLine);

            varName = tokens.at(idx++);
        }
        else if(count == 1)
        {
            if(tokens.at(idx) != "is")
                errorMessage(ERR_EXPECTED_SYMBOL, "is", nLine);
            idx++;
        }
        else if(count == 2)
        {
            /* Allocate space for the variable in case of a valid variable type */
            if(tokens.at(idx) == "number" || tokens.at(idx) == "num")
                state = addVar(varName, VAR_NUM, false, "", 0, false);

            else if(tokens.at(idx) == "string" || tokens.at(idx) == "str")
                state = addVar(varName, VAR_STRING, false, "", 0, false);

            else if(tokens.at(idx) == "boolean" || tokens.at(idx) == "bool")
                state = addVar(varName, VAR_BOOL, false, "", 0, false);

            else
                /* Invalid variable type gets handled here */
                errorMessage(ERR_INVALID_VARTYPE, tokens.at(idx), nLine);

            idx++;
        }
        else
            break;
        count++;
    }

    /* Check if all the tokens required by the [var...is...] statement were processed */
    if(count != 3)
        errorMessage(ERR_INVALID_TOKEN_COUNT, "foo", nLine);

    /*
        Check if the variables were added into the symbol table. The boolean variable 'state' will be
        set to 'true' if they were added. Otherwise, the variables already exist in the memory.
    */
    if(!state)
        errorMessage(ERR_DUP_IDENTIFIER, tokens.at(idx), nLine);

    /*
        The index now points at the very next token after the last one (variable type). Hence,
        decrement it by one. The check against the size is done to prevent a SIGSEGV when we try to
        access the 'idx'-th element of token, 'idx' being a value near the end of the list.
    */
    if(idx < tokens.size() && tokens.at(idx) != ";")
        idx--;
}

void deleteVar(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    /*
        This command has the following syntax:
        delete <variable name>;

        The index is currently pointing to the keyword 'delete'
    */

    /* Try to increment the index. It will now point to the variable name. */
    (unsigned int)(idx+1) < tokens.size() ? ++idx : idx, errorMessage(ERR_EXPECTED_IDENT, "foo", nLine);

    if(!removeVar(tokens.at(idx)))
        errorMessage(ERR_INVALID_IDENT, tokens.at(idx), nLine);
    idx++;

    if(idx < tokens.size() && tokens.at(idx) != ";")
        idx--;
}

void exitPrg(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    /*
        Exit has the following syntax:
            exit;
        The index is currently pointing to the keyword 'exit'
    */
    exit(0);            // Bye-bye :D

    // Not really necessary
    if(idx < tokens.size() && tokens.at(idx) != ";")
        idx--;
}

void print(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    string printData;
    bool append = false;
    double value;
    ostringstream strOut;
    bool firstTerm = true;

    /* Try to increment the index. It will now point at whatever comes after 'print'. */
    (unsigned int)(idx+1) < tokens.size() ? ++idx : idx;

    /* Keep parsing until end of line or a semicolon */
    while(idx < tokens.size() && tokens.at(idx) != ";")
    {
        /* The comma can be used to concatenate statements when printing them on the screen */
        if(tokens.at(idx) == "," && !firstTerm)
        {
            idx++;
            append = true;
        }

        /* If the term after the print statement is a number / expression / number variable */
        else if((isVarExist(tokens.at(idx)) != NOT_FOUND && getVarType(tokens.at(idx)) == VAR_NUM) || \
                tokens.at(idx) == "(" || tokens.at(idx) == "+" || tokens.at(idx) == "-" || \
                tokens.at(idx) == "*" || tokens.at(idx) == "/" || isdigit(tokens.at(idx).at(0)))
                {
                    value = getExpr(idx, tokens, nLine);
                    if(bracketBalance > 0)
                        errorMessage(ERR_EXPECTED_SYMBOL, ")", nLine);
                    else
                        strOut<<value;

                    /*
                        Append is set if the comma operator was used. In that case, the new stuff
                        needs to be appended.
                    */
                    if(!append && firstTerm)
                    {
                        printData = strOut.str();
                        firstTerm = false;
                    }
                    else if(append)
                    {
                        append = false;
                        printData.append(strOut.str());
                    }
                    else
                        errorMessage(ERR_EXPECTED_SYMBOL, ",", nLine);
                }

        /* Incase the variable after the print statement is a string type variable */
        else if(isVarExist(tokens.at(idx)) != NOT_FOUND && getVarType(tokens.at(idx)) == VAR_STRING)
        {
            /* If the variable is initialized, print it */
            if(isVarInit(tokens.at(idx), nLine))
            {
                    if(!append && firstTerm)
                    {
                        printData = getStrData(tokens.at(idx), nLine);
                        firstTerm = false;
                    }
                    else if(append)
                    {
                        append = false;
                        printData.append(getStrData(tokens.at(idx), nLine));
                    }
                    else
                        errorMessage(ERR_EXPECTED_SYMBOL, ",", nLine);
                idx++;
            }
            else
                errorMessage(ERR_IDENT_UNINIT, tokens.at(idx), nLine);
        }

        /* Incase the variable after the print statement is a boolean */
        else if(isVarExist(tokens.at(idx)) != NOT_FOUND && getVarType(tokens.at(idx)) == VAR_BOOL)
        {
            /* If the variable is initialized, print it */
            if(isVarInit(tokens.at(idx), nLine))
            {
                    if(!append && firstTerm)
                    {
                        printData = getBoolData(tokens.at(idx), nLine) ? "True" : "False";
                        firstTerm = false;
                    }
                    else if(append)
                    {
                        printData.append(getBoolData(tokens.at(idx), nLine) ? "True" : "False");
                        append = false;
                    }
                    else
                        errorMessage(ERR_EXPECTED_SYMBOL, ",", nLine);
                idx++;
            }
            else
                errorMessage(ERR_IDENT_UNINIT, tokens.at(idx), nLine);
        }

        /* For the case of raw string data */
        else if((unsigned char)tokens.at(idx).at(0) == 0xFF)
        {
                /* Don't print the leading marker (0xFF) */
                if(!append && firstTerm)
                {
                    printData = tokens.at(idx++).substr(1);
                    firstTerm = false;
                }
                else if(append)
                {
                    printData.append(tokens.at(idx++).substr(1));
                    append = false;
                }
                else
                    errorMessage(ERR_EXPECTED_SYMBOL, ",", nLine);
        }
        else
            errorMessage(ERR_CUSTOM, "Expected string data or an expression.", nLine);
    }

    /* Check for the end of statement semicolon */
    if(idx < tokens.size() && tokens.at(idx) != ";")
        idx--;

    // Show the data on the standard output
    cout<<printData<<endl;
}

void let(unsigned short int& idx, const std::vector<std::string>& tokens, int& nLine)
{
    /* Try to increment the index. It will now point to the variable name. */
    (unsigned int)(idx+1) < tokens.size() ? ++idx : idx, errorMessage(ERR_EXPECTED_IDENT, "foo", nLine);

    //TODO: write code to parse let

    /* Keep parsing until end of line or a semicolon */
    while(idx < tokens.size() && tokens.at(idx) != ";")
    {
        // Code
    }
}
